#This is the message class. It defines network messages
#message math syntax
#/path/to/recipiant<(protocol) {message}</path/from/sender
require 'thread'
class Message
  #attributes for each message
  #@message_string is the raw string of the message
  #@sending_connection is the connection that sent the message to this peer
  #@next_recipeint is the next peer to send the message to. Both @next_recipeint
  #and @sending_connection are not part of the @message_string
  attr_accessor :message_string, :sending_connection
  attr_reader :next_recipeint
  @@message_queue = Array.new
  @@received_queue = Array.new
  def Message.send_message(recipeint, message, protocol, allow_gourami = false)
    #checks to see if {,},[ or ] are part of any of the part of the message and raises an exception if they are
    raise ArgumentError.new("Recipeint cannot contain {,},[ or ]")  if recipeint.scan(/\{|\}|\[|\]/)
    raise ArgumentError.new("Protocol cannot contain {,},[ or ]") if protocol.scan(/\{|\}|\[|\]/)
    riase ArgumentError.new("Protocol should not be of type GOURAMI. GOURAMI messages should be send with server methods. To override pass true for the allow_gourami argument") if protocol.include?("GOURAMI") && !allow_gourami
    raise ArgumentError.new("Message cannot contain {,},[ and ]") if message.scan(/\{|\}|\[|\]/)
    #adds the message to the queue
    @@message_queue.push(Message.new("#{recipeint}[#{protocol}]{#{message}}"))
  end
  def recipeint_path
    #returns the part of the @message_string that contains the recipeint_path
    #It starts at the beginning of the @message_string and ends just before '['
    #If recipeints exist return them in an array
    if @message_string.index('[') > 0
      @message_string.split('[')[0].split('/')
      #if they don't exist however return a zero length array
    else
      Array.new
    end
  end
  def sender_path
    #return the part of the @message_string that contains the sender_path
    #It starts just after the last '}' and goes through to end of @message_string
    #If senders exist return them in an array
    if @message_string.index('}') < @message_string.length - 1
      @message_string.split('}')[-1].split('/')
    else
      #if they don't exist return a zero length array
      Array.new
    end
  end
  #the getter for the message protocol
  def protocol
    #checks to see if the message has a protocol section with length > 0
    if @message_string.index(']') - @message_string.index('[') > 0
      #gets the string in between the first '[' and the first ']'
      @message_string.split('[')[1].split(']')[0]
    else
      #if it doesn't it doesn't matter as it is zero length so return a zero length string
      ""
    end
  end
  def message_text
    #checks to see if the message text is of a length greater than zero
    if @message_string.index('}') - @message_string.index('{') > 0
      #returns the sectioin between the first '{' and the first '}'
      @message_string.split('{')[1].split('}')[0]
    else
      #if the message_text section is zero length that does not matter as it will just return a zero length string
      ""
    end
  end
  #checks to see if the message has reached it end point.
  #this is only valid internally as it is only vaid before processing
  def end_point?
    #it checks to see if it has reached the end point by checking the recipeint_path length to see if it is zero
    if recipeint_path.length == 0
      true
    else
      false
    end
  end
  #allows a message to be returned (of the same protocol) to its sender
  #this is quite usefull for immediate return of things such as GOURAMI replies.
  def return_message(message_text)
    #creates a new message a uses the sender_path as the receipient path
    #Message.new("#{message.sending_connection.name}[GOURAMI]{SET NAME:#{@name}}") if reg_name == true
    Message.new(sender_path.join('/') + "/" + '[' + protocol + ']{' + message_text + '}' ) if sender_path.length != 0
  end
  #this is used for processing the message. It moves the message to the next user along the path strings
  def iterate(received_from = "")
    @next_recipeint = recipeint_path[-1]
    mes_string = ""
    mes_string += recipeint_path[0..-2].join('/')
    mes_string += '[' + protocol + ']{' + message_text + '}' + ((received_from.length > 0  ? received_from + '/' : '') + sender_path.join('/'))
    @message_string = mes_string
    puts "i>" + @message_string
  end
  #the constructor for the class. It uses the message_string, which is the raw string used to describe the message
  #and the received path. The receive path defaults to a zero length string
  def initialize(message_string, received_from = nil)
    #the message_string is the raw message string. It is what is manipulated by various methods thoughout the life of a message
    @message_string = message_string
    #the connection that sent the message to this peer.
    @sending_connection = received_from
    #checks to see if the message is at its endpoint
    if !end_point?
      #if it is not at the end point iterate it and added it to the message_queue to be relayed
      iterate(received_from != nil ? received_from.name : '')
      @@message_queue.push(self)
    else
      #if it is at the end point add the sending_connection's name to the message string and add it to the received queue
      @message_string << "#{sender_path.length != 0 ? '/' : ''}#{received_from.name}" if received_from != nil
      @@received_queue.push(self)
    end
  end
  #the setter for the message queue
  def Message.message_queue=(m_q)
    Mutex.new.synchronize { @@message_queue = m_q }
  end
  #the setter for the received_messages queue
  def Message.received_messages=(m_q)
    Mutex.new.synchronize { @@received_queue = m_q }
  end
  #the getter for the message queue
  def Message.message_queue
    @@message_queue
  end
  #the getter for the received message queue
  def Message.received_messages
    @@received_queue
  end
  def Message.each_queued_message
    Mutex.new.synchronize do
      for m in @@message_queue
        yield m
      end
    end
  end
  def Message.each_received_message
    Mutex.new.synchronize do
      for m in @@received_queue
        yield m
      end
    end
  end
  private :iterate, :end_point?
end
